// Promise 是异步编程的一种解决方案，比传统的解决方案——回调函数和事件——更合理和更强大
// 所谓Promise，简单说就是一个容器，里面保存着某个未来才会结束的事件（通常是一个异步操作）的结果

// 1、手写 Promise
// 2、手写 Promise.all
// 3、手写 Promise.race

class MyPromise {
  static PENDING = 'PENDING'
  static RESOLVED = 'RESOLVED'
  static REJECTED = 'REJECTED'

  constructor(func) {
    this.status = MyPromise.PENDING
    this.result = null
    this.resolvCallbacks = []
    this.rejectCallbacks = []
    try {
      func(this.resolve.bind(this), this.reject.bind(this))
    } catch (error) {
      // 抛出错误
      this.reject(error)
    }
  }

  resolve(result) {
    setTimeout(() => {
      if (this.status === MyPromise.PENDING) {
        this.status = MyPromise.RESOLVED
        this.result = result

        this.resolvCallbacks.forEach(callback => {
          callback(result)
        })
      }
    });
  }

  reject(result) {
    setTimeout(() => {
      if (this.status === MyPromise.PENDING) {
        this.status = MyPromise.REJECTED
        this.result = result

        this.rejectCallbacks.forEach(callback => {
          callback(result)
        })
      }
    });
  }

  then(onResolve, onReject) {
    return new MyPromise(() => {
      onResolve = typeof onResolve === 'function' ? onResolve : () => { }
      onReject = typeof onReject === 'function' ? onReject : () => { }

      if (this.status === MyPromise.PENDING) {
        this.resolvCallbacks.push(onResolve)
        this.rejectCallbacks.push(onReject)
      }

      if (this.status === MyPromise.RESOLVED) {
        // 将同步改为异步
        setTimeout(() => {
          onResolve(this.result)
        })
      }

      if (this.status === MyPromise.REJECTED) {
        // 将同步改为异步
        setTimeout(() => {
          onReject(this.result)
        })
      }
    })
  }
}

var p = new MyPromise(resolve => {
  resolve('aaaa')
})
var p = new MyPromise(resolve => {
  throw new Error('错误')
  resolve('aaaa')
})
var p = new MyPromise(resolve => {
  setTimeout(() => {
    resolve('aaaa')
  });
})

function myPromiseAll(list) {
  return new Promise((resolve) => {
    const resArr = [];
    const num = 0;
    list.forEach((item) => {
      item.then((res) => {
        resArr.push(res);
        num += 1;

        if (num === list.length) {
          resolve(resArr);
        }
      });
    });
  });
}

function myPromiseRace(list) {
  return new Promise((resolve, reject) => {
    list.forEach(item => {
      item.then(res => {
        resolve(res)
      }, err => {
        reject(err)
      })
    })
  })
}

const p1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(11111);
  }, 1000);
});

const p2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve(22222);
  }, 2000);
});

Promise.all([p1, p2]).then((res) => {
  console.log(res);
});

Promise.race([p1, p2]).then((res) => {
  console.log(res);
});

/**
 * 成功后 resolve 结果，失败后重试，尝试超过一定次数才真正的 reject
 * @param {*} promiseFn 
 * @param {*} times 
 * @returns 
 */
Promise.retry = function (promiseFn, times = 3) {
  return new Promise(async (resolve, reject) => {
    while (times--) {
      try {
        var ret = await promiseFn();
        resolve(ret);
        break;
      } catch (error) {
        if (!times) reject(error);
      }
    }
  });
};
function getProm() {
  const n = Math.random();
  return new Promise((resolve, reject) => {
    setTimeout(() => n > 0.9 ? resolve(n) : reject(n), 1000);
  });
}
Promise.retry(getProm);

/**
 * 业务需求中，经常有 只需要最后一次请求的结果（比如搜索）编写一个高阶函数，传递旧请求方法（执行后返回 promise），返回一个新方法。
 * 连续触发时，若上一次 promise 执行未结束则直接废弃，只有最后一次 promise 会触发then/reject。
 * @param {*} promiseFunction 
 * @returns 
 */
function lastPromise(promiseFunction) {//骚操作版
  // TODO
  lastPromise.target1 = 0;
  lastPromise.target2 = 0;

  return function () {
    lastPromise.target1++;
    return new Promise((resolve, reject) => {
      promiseFunction().then(v => {
        lastPromise.target2++;
        if (lastPromise.target1 === lastPromise.target2) {
          resolve(v)
        }
      })
    })
  }
}

var count = 1;
var promiseFunction = () =>
  new Promise(rs =>
    setTimeout(() => {
      rs(count++);
    })
  );
var lastFn = lastPromise(promiseFunction);

lastFn().then(console.log); // 无输出
lastFn().then(console.log); // 无输出
lastFn().then(console.log); // 3

/**
 * 业务需求中，经常有只需要请求一次，以防止用户重复点击行为导致的触发重复请求。
 * 传递请求方法（执行后返回promise），返回一个新方法。连续触发时，只执行一次。
 * 原理：将请求的实例先存储起来，而成功和失败内部都可以感知到，进而将其重新置空，接受下一次请求。
 * @param {*} promiseFunction 
 * @returns 
 */
function firstPromise(promiseFunction) {
  let p = null;
  return function (...args) {
    // 请求的实例，已存在意味着正在请求中，直接返回实例，不触发新的请求
    return p
      ? p
      // 否则发送请求，且在finally时将p置空，那么下一次请求可以重新发起
      : (p = promiseFunction.apply(this, args).finally(() => (p = null)));
  };
}

// 示例
var count = 1;
var promiseFunction = () =>
  new Promise(rs =>
    setTimeout(() => {
      rs(count++);
    })
  );
var firstFn = firstPromise(promiseFunction);
firstFn().then(console.log); // 1
firstFn().then(console.log); // 1
firstFn().then(console.log); // 1